Skip to content

feat(payments): Add LangGraph integration for payment handling - #546

Merged
aidandaly24 merged 22 commits into
aws:mainfrom
ragsu43:ragsu/langgraph-integration
Jul 7, 2026
Merged

feat(payments): Add LangGraph integration for payment handling#546
aidandaly24 merged 22 commits into
aws:mainfrom
ragsu43:ragsu/langgraph-integration

Conversation

@ragsu43

@ragsu43 ragsu43 commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Description of changes:

Added LangGraph middleware and config files for developer integration with ACP.

Comment thread src/bedrock_agentcore/payments/integrations/langgraph/config.py Outdated
Comment thread src/bedrock_agentcore/payments/integrations/langgraph/errors.py
Comment thread src/bedrock_agentcore/payments/integrations/langgraph/middleware.py Outdated
  Merge AgentCorePaymentsConfig (LangGraph) and AgentCorePaymentsPluginConfig
  (Strands) into a single dataclass in integrations/config.py. Both names
  remain available as aliases for backward compatibility.
…cation

Extract guard checks, 402 detection, payment request extraction, header
injection, and post-payment rejection detection into shared private
methods. The sync and async paths now only differ at await/sleep
boundaries. Future bug fixes to detection or injection logic only need
to be applied in one place instead of four.

No behavioral changes — 128 tests pass identically before and after.
@ragsu43
ragsu43 force-pushed the ragsu/langgraph-integration branch from 17b79dc to b44cf37 Compare July 2, 2026 01:52
Document when each path is used (.invoke vs .ainvoke), what the async
path does differently (non-blocking sleep, to_thread for signing, async
callbacks), and provide FastAPI and script examples.

@aidandaly24 aidandaly24 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work on this. The overall flow (detect, sign, inject, retry, across sync and async, plus the recovery callback and auto-session) reads well and the unit coverage is thorough. Requesting changes on a couple of correctness issues before this goes in, plus some cleanup.

Blocking:

  • Sync path silently drops async on_payment_error callbacks (middleware.py:555).
  • _check_post_recovery_rejection surfaces the wrong error detail because it never swaps in the fallback handler (middleware.py:373).

Should fix:

  • Detection ignores the name-based handler registry, so legacy text-format 402s get missed (middleware.py:267).
  • The exported MCPRequestPaymentHandler can't actually be used as a custom_handlers entry because of a shape mismatch (middleware.py:269).

Cleanup:

  • The sync/async and the two rejection-check methods are near-duplicates and have already drifted, worth collapsing (middleware.py:353).
  • Move the inline imports up to the top of the file per our style (middleware.py, several spots).

One process note: the header on test_functional.py says any change to this middleware has to be validated against a live Base Sepolia testnet before merge. Please confirm that run has happened.

Comment thread src/bedrock_agentcore/payments/integrations/langgraph/middleware.py
Comment thread src/bedrock_agentcore/payments/integrations/langgraph/middleware.py
Comment thread src/bedrock_agentcore/payments/integrations/langgraph/middleware.py
Comment thread src/bedrock_agentcore/payments/integrations/langgraph/middleware.py Outdated
Comment thread src/bedrock_agentcore/payments/integrations/langgraph/middleware.py Outdated
Comment thread src/bedrock_agentcore/payments/integrations/langgraph/middleware.py Outdated
ragsu43 and others added 10 commits July 6, 2026 20:50
If an async def on_payment_error callback is registered but the agent
runs via .invoke() (sync path), the callback would silently return an
unawaited coroutine, bypass RETRY logic, and leak a RuntimeWarning.

Now we detect this with inspect.iscoroutinefunction() and raise a clear
TypeError that the existing try/except catches and logs — the agent
continues with the default error message and the developer sees exactly
what to fix in their logs.
…path

Verify that when a raw-JSON tool returns 402 after error handler
recovery, the FallbackHandler is used to extract the real error detail
(e.g. 'budget exceeded') instead of falling through to 'unknown'.
When fallback detects a 402 in the recovery retry path, reassign _rh to
_FallbackHandler(fallback) so extract_body returns the actual parsed
body. Without this, GenericPaymentHandler looks for the PAYMENT_REQUIRED
marker, finds nothing in raw JSON, and the LLM sees 'unknown' instead
of the real error detail.
When both GenericPaymentHandler (marker) and _fallback_detect_402 (JSON)
fail to detect a 402, try the name-based handler from get_payment_handler
(e.g. HttpRequestPaymentHandler for tools named http_request). This
covers the legacy 'Status Code: 402' text-block format used by Strands
tools ported to LangGraph without format adaptation.
…hape

Custom handlers now receive result.content (the raw ToolMessage content)
for extract_status_code, extract_headers, and extract_body — not the
internal {'content': [{'text': ...}]} prepared shape.

This makes the custom handler contract intuitive: handlers parse the
tool's actual output format, not a middleware-internal wrapper. Fixes
silent detection failures when custom handlers expected raw JSON or
other native formats.
Add tests for:
- Async auto_session creation and reuse
- Async post-payment rejection with raw JSON fallback
- Async name-based handler fallback (legacy text-block format)
- Async custom handler receiving raw content
- Async error handler callback (retry and propagate paths)
- Sync custom handler raw content contract verification

Suite now at 141 passed, providing async path parity with sync tests.
- Merge _check_post_payment_rejection and _check_post_recovery_rejection
  into single _check_retry_rejection with context parameter
- Extract _inject_for_error_retry for shared injection in error handlers
- Extract _build_error_context for shared PaymentErrorContext construction
- Extract _handle_callback_resolution for shared resolution dispatch

Reduces middleware.py from 805 to 704 lines. The sync/async error
handlers now only differ at await/sleep/to_thread boundaries.
141 tests pass identically.
Move asyncio, json, GenericPaymentHandler, ErrorResolution, and
PaymentErrorContext imports to the top of the file. None have circular
dependency risks. Eliminates repeated imports that contributed to
drift between duplicated code blocks.
… contract

Update TrackingHandler and RawJsonHandler in test_functional.py to
handle raw content (str/list) instead of the prepared shape dict.
Required after the custom handler contract change in 076d6ed.

@aidandaly24 aidandaly24 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the fixes, approved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants